//
// Created by vivi on 11/09/2017.
//

#ifndef HAIRCORE_HAIRBASESIMULATOR_H
#define HAIRCORE_HAIRBASESIMULATOR_H

#include <functional>
#include <vector>
#include "HairSimulator.h"
#include "HairReader.h"
#include "HairStictionSolver.h"

namespace XRwy {
	struct ParticleView {

		Eigen::Vector3f *pos_ptr;
		Eigen::Vector3f *vel_ptr;

		inline Eigen::Vector3f &pos() const {
			return *pos_ptr;
		}

		inline Eigen::Vector3f &vel() const {
			return *vel_ptr;
		}

		Eigen::Vector3f rest_pos;

		bool is_visible;

		//which strand it is belong to
		size_t particle_id;
		size_t strand_id;
		size_t local_id;

		ParticleView(Eigen::Vector3f *pos_ptr_, Eigen::Vector3f *vel_ptr_, bool is_visible_):
				pos_ptr(pos_ptr_), vel_ptr(vel_ptr_), is_visible(is_visible_) {
		}
	};

	//using SegmentView = pair<ParticleView*, ParticleView*>;
	struct SegmentView {
		size_t id1, id2;

		//which strand it is belong to
		size_t strand_id;
	};

	struct StrandView {
		std::vector<size_t> all_ids;
		std::vector<size_t> visible_ids;

		//which strand it is
		size_t strand_id;
	};

	struct HairBaseSimulatorConfiguration {
		HairBaseSimulatorConfiguration(const HairSimulatorConfDict &d) {
			HairSimulatorConfDictHelperUtility u;

			enable_body_collision = u.get_bool(d, "enable_body_collision");
			enable_hair_stiction = u.get_bool(d, "enable_hair_stiction");
			enable_npass = u.get_bool(d, "enable_npass");
			npass = u.get_int(d, "npass");

			stiction_type = u.get_string(d, "stiction_type");

			cage_stiction_rate = u.get_float(d, "cage_stiction_rate");

			density_stiction_rate = u.get_float(d, "density_stiction_rate");
			density_cell_count = static_cast<size_t>(u.get_int(d, "density_cell_count"));

			spring_distance = u.get_float(d, "spring_distance");
			spring_tolerance = u.get_float(d, "spring_tolerance");
			spring_acc = u.get_float(d, "spring_acc");
		}

		bool enable_body_collision;
		bool enable_hair_stiction;

		bool enable_npass;
		int npass;

		std::string stiction_type;

		//stiction solver - cage
		float cage_stiction_rate;

		//stiction solver - density field
		float density_stiction_rate;
		size_t density_cell_count;

		//stiction solver - spring
		float spring_distance;
		float spring_tolerance;
		float spring_acc;

		virtual ~HairBaseSimulatorConfiguration() = default;
	};

	//this class handle the operation like collision detection and
	//collision correction and friction solver
	//subclass should implement the "strand_dynamic" and "particle_init"
	//the "particle_init" set up the ParticleView, SegmentView and StrandView
	//the "strand_dynamic" handles the dynamic of each strand
	class HairBaseSimulator: public HairSimulator {
		friend class HairStictionSolver;
		friend class HairStictionCageSolver;
		friend class HairStictionDensityFieldSolver;
		friend class HairStictionSpringSolver;
	public:
		HairBaseSimulator(HairBaseSimulatorConfiguration *conf_ptr);

		virtual void on_frame(const HairSimulatorData &data);

		virtual int get_particle_count() const {
			return particles.size();
		}

		virtual int get_strand_count() const {
			return strands.size();
		}

		virtual int get_particle_count_on_strand(int idx) const {
			return strands[idx].visible_ids.size();
		}

		virtual void copy_particles(float *positions) const {
			for (const auto & particle : particles) {
				if (!particle.is_visible)
					continue;

				const auto & pos = particle.pos();
				positions[0] = pos(0);
				positions[1] = pos(1);
				positions[2] = pos(2);
				positions += 3;
			}
		}

		virtual ~HairBaseSimulator() {
			delete this->conf_ptr;
			delete this->stiction_solver_ptr;
		}

		virtual void preprocess(const HairSimulatorData &data);

	protected:
		virtual void strand_dynamic(const Eigen::Affine3f &transformation, const float t) = 0;

		virtual void on_collision(const HairSimulatorData &data, const Eigen::Affine3f &transformation, const float t);
		virtual void on_stiction(const HairSimulatorData &data, const Eigen::Affine3f &transformation, const float t);
		//a helper function to push a strand's particle to particles, segments and strands
		void push_strand(const std::vector<ParticleView> & ps) {
			StrandView current_strand;
			size_t current_strand_id = strands.size();
			current_strand.strand_id = current_strand_id;

			for (size_t i = 0; i < ps.size(); ++i) {
				particles.push_back(ps[i]);

				auto & current_particle = particles.back();

				current_particle.rest_pos = current_particle.pos();
				current_particle.strand_id = current_strand_id;
				current_particle.particle_id = particles.size() - 1;
				current_particle.local_id = i;

				if (i > 0)
					//the last two particles' global id is particle.size() - 2, particle.size() - 1
					segments.push_back(SegmentView{current_particle.particle_id - 1, current_particle.particle_id, current_strand_id});

				current_strand.all_ids.push_back(current_particle.particle_id);
				if (current_particle.is_visible) {
					current_strand.visible_ids.push_back(current_particle.particle_id);
				}
			}

			strands.push_back(std::move(current_strand));
		}

		std::vector<ParticleView> particles;
		std::vector<SegmentView> segments;
		std::vector<StrandView> strands;

		Eigen::Affine3f previous_transform = Eigen::Affine3f::Identity();

		HairBaseSimulatorConfiguration *conf_ptr;

		HairStictionSolver *stiction_solver_ptr;
	};
}

#endif //HAIRCORE_HAIRBASESIMULATOR_H
